package checker

import (
	"sort"
	"kumachan/standalone/util"
	"kumachan/standalone/rpc"
	"kumachan/standalone/rpc/vd"
	"kumachan/interpreter/lang/textual/ast"
	"kumachan/interpreter/lang/common/attr"
	"kumachan/interpreter/lang/common/name"
	"kumachan/interpreter/lang/common/limits"
	"kumachan/interpreter/lang/common/source"
	"kumachan/interpreter/compiler/loader"
	"kumachan/interpreter/compiler/checker/typsys"
	"kumachan/interpreter/compiler/checker/checked"
	"path/filepath"
)


type Function struct {
	attr.FunctionAttrs
	AstNode     *ast.DeclFunction
	Exported    bool
	Name        name.FunctionName
	Signature   FunctionSignature
	Body        FunctionBody
}
type FunctionSignature struct {
	TypeParameters   [] typsys.Parameter
	ImplicitContext  typsys.Record
	InputOutput      typsys.Lambda
}
type FunctionBody interface { functionBody() }
func (OrdinaryBody) functionBody() {}
type OrdinaryBody struct {
	Inject  [] *checked.LocalBinding
	Lambda  checked.Lambda
}
func (NativeBody) functionBody() {}
type NativeBody struct {
	Id  string
}
func (AssetBody) functionBody() {}
type AssetBody struct {
	Asset
}
func (SerializerBody) functionBody() {}
type SerializerBody struct {
	Type  *vd.Type
}
func (DeserializerBody) functionBody() {}
type DeserializerBody struct {
	Type  *vd.Type
}
func (ServiceIdentifierBody) functionBody() {}
type ServiceIdentifierBody struct {
	Id  rpc.ServiceIdentifier
}

type FunctionRegistry (map[name.Name] ([] *Function))
type functionList ([] *Function)
func (l functionList) Less(i int, j int) bool {
	var u = l[i].Name
	var v = l[j].Name
	if u.ModuleName < v.ModuleName {
		return true
	} else if u.ModuleName == v.ModuleName {
		if u.ItemName < v.ItemName {
			return true
		} else if u.ItemName == v.ItemName {
			return (u.Tag < v.Tag)
		} else {
			return false
		}
	} else {
		return false
	}
}
func (l functionList) Len() int {
	return len(l)
}
func (l functionList) Swap(i int, j int) {
	var I = &(l[i])
	var J = &(l[j])
	var t = *I
	*I = *J
	*J = t
}

var bodyInit, bodyWrite = (func() (FunctionBody, func(FunctionBody)(FunctionBody)) {
	return nil, func(body FunctionBody) FunctionBody { return body }
})()

func collectFunctions (
	entry  *loader.Module,
	mic    ModuleInfoCollection,
	types  TypeRegistry,
	types_ typeList,
) (FunctionRegistry, functionList, DispatchMapping, *RpcInfo, source.Errors) {
	var reg = make(FunctionRegistry)
	var mvs = make(ModuleVisitedSet)
	var err1 = registerFunctions(entry, mic, mvs, reg, types)
	if err1 != nil { return nil, nil, nil, nil, err1 }
	var err2 = collectTypesDefaultValues(&types_, reg)
	if err2 != nil { return nil, nil, nil, nil, err2 }
	var dm, err3 = generateDispatchMapping(types_, reg)
	if err3 != nil { return nil, nil, nil, nil, err3 }
	var ri, err4 = generateRpcInfo(mic, types_, types, &reg)
	if err4 != nil { return nil, nil, nil, nil, err4 }
	var functions = make(functionList, 0)
	for _, group := range reg {
		for _, f := range group {
			functions = append(functions, f)
		}
	}
	sort.Sort(functions)
	var all_reg = &Registry {
		Types:     types,
		Functions: reg,
	}
	var step = func(f func(*Function) *source.Error) source.Errors {
		var errs source.Errors
		for _, function := range functions {
			source.ErrorsJoin(&errs, f(function))
		}
		return errs
	}
	var step0_generate_body = step
	{ var err = step0_generate_body(func(f *Function) *source.Error {
		if f.Body != nil {
			// body already generated
			return nil
		}
		var body, err = (func() (FunctionBody, *source.Error) {
			switch body := f.AstNode.Body.Body.(type) {
			case ast.NativeRef:
				return NativeBody { Id: string(body.Id.Value) }, nil
			case ast.Lambda:
				var implicit = f.Signature.ImplicitContext.Fields
				var inj = make([] *checked.LocalBinding, len(implicit))
				for i, field := range implicit {
					inj[i] = &checked.LocalBinding {
						Name:     field.Name,
						Type:     field.Type,
						Location: field.Attr.Location,
					}
				}
				var ctx = ExprContext {
					ExprContextBase: &ExprContextBase {
						Registry:        all_reg,
						ModuleInfo:      mic.GetFunctionModuleInfo(f),
						ParameterList:   f.Signature.TypeParameters,
						DispatchMapping: dm,
					},
				}
				var expected = &typsys.NestedType {
					Content: f.Signature.InputOutput,
				}
				var expr, _, err = checkLambda(body, inj)(expected, nil, ctx)
				if err != nil { return nil, err }
				var lambda = expr.Content.(checked.Lambda)
				if lambda.Self != nil {
					return nil, source.MakeError(body.Location,
						E_SelfRefBindingOnTopLevel {})
				}
				return OrdinaryBody {
					Inject: inj,
					Lambda: lambda,
				}, nil
			case ast.LoadedAsset:
				var declared_t = f.Signature.InputOutput.Output
				var data = body.Bytes
				var path = body.Path
				var ext = filepath.Ext(path)
				var p, exists = assetProcessorsMap[ext]
				if exists {
					var data, err = (func() ([] byte, error) {
						if p.Transform != nil {
							return p.Transform(data)
						} else {
							return data, nil
						}
					})()
					if err != nil {
						return nil, source.MakeError(body.Location,
							E_InvalidAsset {
								Error: err,
							})
					}
					var required_t = p.Type(types)
					if !(typsys.TypeOpEqual(declared_t, required_t)) {
						return nil, source.MakeError(f.AstNode.InOut.Location,
							E_WrongAssetType {
								Extension: ext,
								Required:  typsys.DescribeType(required_t, nil),
							})
					}
					return AssetBody { Asset {
						MIME: p.MIME,
						Path: path,
						Data: data,
					} }, nil
				}
				var binary_t = coreAssetBinary(types)
				if !(typsys.TypeOpEqual(declared_t, binary_t)) {
					return nil, source.MakeError(f.AstNode.InOut.Location,
						E_WrongAssetType {
							Extension: ext,
							Required:  typsys.DescribeType(binary_t, nil),
						})
				}
				return AssetBody { Asset {
					MIME: "application/octet-stream",
					Path: path,
					Data: data,
				} }, nil
			case ast.InvalidAsset:
				return nil, source.MakeError(body.Location,
					E_InvalidAsset {
						Error: body.Error,
					})
			default:
				panic("impossible branch")
			}
		})()
		if err != nil { return err }
		f.Body = bodyWrite(body)
		return nil
	})
	if err != nil { return nil, nil, nil, nil, err } }
	return reg, functions, dm, ri, nil
}

func registerFunctions (
	mod    *loader.Module,
	mic    ModuleInfoCollection,
	mvs    ModuleVisitedSet,
	reg    FunctionRegistry,
	types  TypeRegistry,
) source.Errors {
	return traverseStatements(mod, mic, mvs, func(stmt ast.VariousStatement, mi *ModuleInfo) *source.Error {
		switch result := unifyFunction(stmt.Statement, mi, types).(type) {
		case funcUnifySuccess:
			var decl = result.decl
			var _, err = registerFunction(decl, mi, reg, types)
			return err
		case funcUnifyFailure:
			var err = result.err
			return err
		case funcUnifyNonFunction:
			return nil
		default:
			panic("impossible branch")
		}
	})
}

func registerFunction (
	decl   *ast.DeclFunction,
	mi     *ModuleInfo,
	reg    FunctionRegistry,
	types  TypeRegistry,
) (*Function, *source.Error) {
	var loc = decl.Name.Location
	var func_item_name = ast.Id2String(decl.Name)
	if !(isValidFunctionItemName(func_item_name)) {
		return nil, source.MakeError(loc,
			E_InvalidFunctionName { Name: func_item_name })
	}
	var n = name.MakeName(mi.ModName, func_item_name)
	var f = new(Function)
	var existing, _ = reg[n]
	reg[n] = append(existing, f)
	var index = uint(len(existing))
	var tag = name.FunctionNameTagFromSource(index)
	var func_name = name.FunctionName { Name: n, Tag: tag }
	var doc = ast.GetDocContent(decl.Docs)
	var meta attr.FunctionMetadata
	var meta_text = ast.GetMetadataContent(decl.Meta)
	var meta_err = util.UnmarshalJsonAllowEmpty(meta_text, &meta)
	if meta_err != nil {
		return nil, source.MakeError(decl.Meta.Location,
			E_InvalidMetadata { Reason: meta_err.Error() })
	}
	var attrs = attr.FunctionAttrs {
		Attrs:    attr.Attrs {
			Location: loc,
			Doc:      doc,
		},
		Metadata: meta,
	}
	var params_, params_err = (func() ([] typsys.Parameter, *source.Error) {
		var bound_ctx = TypeConsContext {
			ModInfo: mi,
			TypeReg: types,
		}
		var arity = len(decl.Params)
		if arity > limits.MaxTypeParameters {
			return nil, source.MakeError(loc,
				E_TooManyTypeParameters { SizeLimitError {
					Given: uint(arity),
					Limit: limits.MaxTypeParameters,
				}})
		}
		var params = make([] typsys.Parameter, arity)
		for i, p := range decl.Params {
			var p_name = ast.Id2String(p.Name)
			if !(isValidTypeItemName(p_name)) {
				return nil, source.MakeError(p.Name.Location,
					E_InvalidTypeName { Name: p_name })
			}
			var bound, err = (func() (typsys.Bound, *source.Error) {
				switch bound := p.Bound.Bound.(type) {
				case ast.TypeLowerBound:
					var v, err = newType(bound.Value, bound_ctx)
					if err != nil { return typsys.Bound{}, nil }
					if typsys.TypeOpEqual(v, typsys.BottomType {}) {
						return typsys.Bound {
							Kind: typsys.OpenBottomBound,
						}, nil
					} else {
						return typsys.Bound {
							Kind:  typsys.InfBound,
							Value: v,
						}, nil
					}
				case ast.TypeHigherBound:
					var v, err = newType(bound.Value, bound_ctx)
					if err != nil { return typsys.Bound{}, nil }
					if typsys.TypeOpEqual(v, typsys.TopType {}) {
						return typsys.Bound {
							Kind:  typsys.OpenTopBound,
						}, nil
					} else {
						return typsys.Bound {
							Kind:  typsys.SupBound,
							Value: v,
						}, nil
					}
				default:
					return typsys.Bound {
						Kind: typsys.NullBound,
					}, nil
				}
			})()
			if err != nil { return nil, err }
			params[i] = typsys.Parameter {
				Name:  p_name,
				Bound: bound,
			}
		}
		return params, nil
	})()
	if params_err != nil { return nil, params_err }
	var exported = decl.Public
	var ctx = TypeConsContext {
		ModInfo:  mi,
		TypeReg:  types,
		ParamVec: params_,
	}
	var implicit, implicit_err = (func() (typsys.Record, *source.Error) {
		var t, err = newTypeFromRepr(decl.Implicit, ctx)
		if err != nil { return typsys.Record {}, err }
		return t.(*typsys.NestedType).Content.(typsys.Record), nil
	})()
	if implicit_err != nil { return nil, implicit_err }
	var io, io_err = (func() (typsys.Lambda, *source.Error) {
		if decl.Kind == ast.FK_Entry {
			return typsys.Lambda {
				Input:  typsys.UnitType {},
				Output: entryEffectType(types),
			}, nil
		} else {
			var io_node = decl.InOut
			var t, err1 = newTypeFromRepr(io_node.Repr, ctx)
			if err1 != nil { return typsys.Lambda {}, err1 }
			var io = t.(*typsys.NestedType).Content.(typsys.Lambda)
			var err2 = collectFuncInputDefaultValues (
				reg,
				&(io.Input),
				io_node.Defaults,
				io_node.Location,
				params_,
				func_name.Name,
				exported,
			)
			if err2 != nil { return typsys.Lambda {}, err2 }
			return io, nil
		}
	})()
	if io_err != nil { return nil, io_err }
	var implicit_t = &typsys.NestedType {
		Content: implicit,
	}
	var in_t = io.Input
	var out_t = io.Output
	var sig_t = &typsys.NestedType { Content:
		typsys.Lambda {
			Input:  implicit_t,
			Output: &typsys.NestedType { Content:
				typsys.Lambda {
					Input:  in_t,
					Output: out_t,
				} },
		},
	}
	var v = typsys.GetAllVariance(sig_t, params_)
	for i := range params_ {
		params_[i].Variance = v[i]
	}
	var params = params_
	*f = Function {
		FunctionAttrs: attrs,
		AstNode:       decl,
		Exported:      exported,
		Name:          func_name,
		Signature:     FunctionSignature {
			TypeParameters:  params,
			ImplicitContext: implicit,
			InputOutput:     io,
		},
		Body: bodyInit,
	}
	return f, nil
}


